﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay.Filters;
using System.Reflection;
using Microsoft.Research.DynamicDataDisplay;
//using System.Data.SqlClient;
using System.Data;
using Microsoft.SqlServer.Types;

namespace Microsoft.Research.DynamicDataDisplay.Charts
{
    public class GeometryViewer  : ViewportElement2D
	{

		#region Filtering quality

		public static readonly DependencyProperty FilteringQualityProperty =
			DependencyProperty.Register(
			"FilteringQuality",
			typeof(double),
			typeof(GeometryViewer),
			new FrameworkPropertyMetadata(
				178.0,
				FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
				OnFilteringQualityChanged),
			OnValidateFilteringQuality);

		public double FilteringQuality
		{
			get { return (double)GetValueSync(FilteringQualityProperty); }
			set { SetValueAsync(FilteringQualityProperty, value); }
		}

		private static bool OnValidateFilteringQuality(object value)
		{
			double degrees = (double)value;
			return degrees >= 0 && degrees <= 180;
		}

		private static void OnFilteringQualityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			GeometryViewer coast = (GeometryViewer)d;
			coast.Filter();
		}

		#endregion

        SolidColorBrush penBrush = Brushes.Black;
        bool slopeUpLeftToRightDown = true;
        Rect boundingBox = new Rect();
        public Rect BoundingBox
        {
            get { return boundingBox; }
            //set { boundingBox = value; }
        }

        SqlGeometry sqlGeom = new SqlGeometry();
        /*public SqlGeometry SQLGeom
        {
            get { return sqlGeom; }
            set { sqlGeom = value; }
        }*/



        private void Init()
        {

            if (!sqlGeom.IsNull)
            {
                boundingBox = GetBoundingBox(sqlGeom);
                DecodeSqlGeometry(sqlGeom);
            }
            RenderTarget = RenderTo.Screen;
            ManualTranslate = true;
        }
		public GeometryViewer()
		{
			Init();
		}

        private void DecodeSqlGeometry(SqlGeometry g)
        {
            List<Point> currCurve = new List<Point>();
            Point p;
            switch (g.STGeometryType().Value.ToLower())
            {
                case "point":
                    p = new Point(g.STX.Value, g.STY.Value);
					currCurve.Add(p);
                    break;
                case "linestring":
                    for (int i = 1; i <= g.STNumPoints(); i++)
                    {
                        p = new Point(g.STPointN(i).STX.Value, g.STPointN(i).STY.Value);
                        currCurve.Add(p);
                    }
                    break;
                case "polygon":
                    string cmd = new string(g.STAsText().Value).Trim().Substring(8);
                    string[] polyArray = (cmd.Substring(1, cmd.Length - 2) + ", ").Split('(');

                    var polys = from s in polyArray
                                where s.Length > 0
                                select s.Trim().Substring(0, s.Length - 3);
                    foreach (var item in polys)
                    {
                        var polyPoints = from pol in item.Split(',')
                                         select pol.Trim().Replace(" ", ",");
                        for (int i = 0; i < polyPoints.Count(); i++)
                        {
                            p = Point.Parse(polyPoints.ElementAt(i));
                            currCurve.Add(p);
                        }
                        points.Add(currCurve);
                        currCurve = new List<Point>();
                    }
                    AddStripeBrush(g, slopeUpLeftToRightDown);

                    break;
                case "multipoint":
                case "multilinestring":
                case "multipolygon":
                case "geometrycollection":
                    for (int i = 1; i <= g.STNumGeometries().Value; i++)
                        DecodeSqlGeometry(g.STGeometryN(i));
                    break;
                default:
                    break;
            }
            if (currCurve.Count > 0)
            {
                points.Add(currCurve);
                currCurve = new List<Point>();
            }
            initialPoints = new List<List<Point>>(points);
            Filter();
        }

        private Rect GetBoundingBox(SqlGeometry g)
        {
            SqlGeometry envelope = g.STEnvelope();
            double Left = envelope.STPointN(1).STX.Value;
            double Bottom = envelope.STPointN(1).STY.Value;
            double Right = envelope.STPointN(3).STX.Value;
            double Top = envelope.STPointN(3).STY.Value;
            Rect rect = new Rect(new Point(Left, Bottom), new Point(Right, Top));
            return rect;
        }

        private void AddStripeBrush(SqlGeometry sqlGg,  bool slopeUpLeftToRightDown = true, int linesQuantity = 200)
        {
            // Get bounding box characteristics

            /*SqlGeometry envelope = sqlGg.STEnvelope();
            double Left = envelope.STPointN(1).STX.Value;
            double Bottom = envelope.STPointN(1).STY.Value;
            double Right = envelope.STPointN(3).STX.Value;
            double Top = envelope.STPointN(3).STY.Value;*/
            
            double lineStartX = boundingBox.Left - (boundingBox.Width);
            double lineStartY = boundingBox.Bottom;
            double lineEndX = boundingBox.Left;
            double lineEndY = boundingBox.Top;
            double lineStep = (boundingBox.Width + boundingBox.Height) / linesQuantity;
            if (!slopeUpLeftToRightDown)
            {
                lineStartY = boundingBox.Top;
                lineEndY = boundingBox.Bottom;
            }

            // Create [linesQuantity] sloping lines (45 degree) which cover geometry bounding box
            SqlGeometry sqlSloppingLines = new SqlGeometry();
            for (int i = 0; i < linesQuantity; i++)
            {
                SqlGeometry line = SqlGeometry.Parse(String.Format("LINESTRING({0} {1}, {2} {3})",
                    (lineStartX + i * lineStep).ToString("#0.000000").Replace(",", "."),
                    lineStartY.ToString("#0.000000").Replace(",", "."),
                    (lineEndX + i * lineStep).ToString("#0.000000").Replace(",", "."),
                    lineEndY.ToString("#0.000000").Replace(",", ".")));
                sqlSloppingLines = sqlSloppingLines.STUnion(line);
            }
            // Get interseption of source geometry and sliping lines
            SqlGeometry intersection = sqlGg.STIntersection(sqlSloppingLines);
            DecodeSqlGeometry(intersection);
            //DecodeSqlGeometry(sqlSloppingLines);
        }
        public GeometryViewer(SqlGeometry SQLGeom, SolidColorBrush penBrush, bool slopeUpLeftToRightDown)
		{
            this.sqlGeom = SQLGeom;
            this.penBrush = penBrush;
            this.slopeUpLeftToRightDown = slopeUpLeftToRightDown;
			Init();
		}

		
		List<List<Point>> initialPoints = new List<List<Point>>();
		List<List<Point>> points = new List<List<Point>>();
		InclinationFilter filter = new InclinationFilter();

		private void Filter()
		{
			filter.CriticalAngle = FilteringQuality;

			// filtering initial points
			List<List<Point>> filteredPoints = new List<List<Point>>();
			foreach (List<Point> pointList in initialPoints)
			{
				List<Point> filteredSeq = filter.Filter(pointList);

				List<Point> seqToFilter = null;
				bool addNeeded = true;
				// checking, if previous points seq ends with the same point that this seq begins
				if (filteredPoints.Count > 0)
				{
					foreach (var seq in filteredPoints)
					{
						if (seq.Count > 0)
						{
							Point lastPoint = seq.Last();
							if (lastPoint == filteredSeq[0])
							{
								// if two seqs can be united, they are.
								seq.AddRange(filteredSeq.Skip(1));
								seqToFilter = seq;
								addNeeded = false;
							}
						}
					}
				}
				// adding new seq, if it was created
				if (filteredSeq.Count > 0 && addNeeded)
				{
					filteredPoints.Add(filteredSeq);
				}
			}

			// final filtering
			for (int i = 0; i < filteredPoints.Count; i++)
			{
				filteredPoints[i] = filter.Filter(filteredPoints[i]);
			}

#if DEBUG
			int sum = filteredPoints.Aggregate(0, ((s, list) => s += list.Count));
			SetValue(PointsNumberPropertyKey, sum);
#endif
			points = filteredPoints;

			Update();
			//MakeDirty();
		}

#if DEBUG
		#region PointsNumber
		private static readonly DependencyPropertyKey PointsNumberPropertyKey =
			DependencyProperty.RegisterReadOnly(
			"PointsNumber",
			typeof(int),
			typeof(GeometryViewer),
			new FrameworkPropertyMetadata());

		public static readonly DependencyProperty PointsNumberProperty = PointsNumberPropertyKey.DependencyProperty;

		public int PointsNumber
		{
			get { return points.Aggregate(0, ((s, list) => s += list.Count)); }
		}
		#endregion
#endif

		private double Parse(string str)
		{
			return Double.Parse(str, CultureInfo.InvariantCulture);
		}

		protected override void OnRenderCore(DrawingContext dc, RenderState state)
		{
			Rect outputWMargin = state.Output;
			var transform = Viewport.Transform;

			if (outputWMargin.Width == 0 || outputWMargin.Height == 0) return;

			StreamGeometry cachedGeom = new StreamGeometry();
			List<List<Point>> transformedPts = new List<List<Point>>();

			foreach (List<Point> list in points)
			{
				transformedPts.Add(list.DataToScreen(transform));
			}

			cachedGeom = new StreamGeometry();
			using (StreamGeometryContext context = cachedGeom.Open())
			{
				foreach (List<Point> pts in transformedPts)
				{
					context.BeginFigure(pts[0], false, false);
					context.PolyLineTo(pts, true, true);
				}
			}
			cachedGeom.Freeze();
			dc.DrawGeometry(
				Brushes.LightGray,
                new Pen(penBrush, 1),
				cachedGeom);
		}
	}
}
